﻿// Jinx AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;

namespace Jinx.Naming.Paths
{
    public class PathParser
    {
        private static readonly Regex ElementRegex;
        private static readonly Regex NameRegex;

        static PathParser()
        {
            NameRegex = new Regex(@"^([A-Za-z_][A-Za-z0-9_]*)([\[\]0-9]*)");
            ElementRegex = new Regex(@"^\[([0-9]+)\](.*)");
        }

        public static Path Parse(string Path)
        {
            try
            {
                var Parts = Path.Split('.');

                if (Parts.Length < 1)
                    throw new ParseException("There must be at least one dot in a binding path");

                var First = true;

                var NodeList = new List<IPathNode>();

                foreach (var Part in Parts)
                {
                    var NameMatch = NameRegex.Match(Part);

                    if (!NameMatch.Success)
                        throw new IllegalName(Part);

                    AddNameNode(First, NodeList, NameMatch.Groups[1].Value);

                    AddSequenceNodes(NodeList, NameMatch.Groups[2].Value);

                    First = false;
                }

                return new Path(NodeList);
            }
            catch (ParseException e)
            {
                throw;
            }
            catch (Exception e)
            {
                throw new ParseException(e);
            }
        }

        private static void AddSequenceNodes(ICollection<IPathNode> NodeList, string SequencePart)
        {
            Match Match;

            while ((Match = ElementRegex.Match(SequencePart)).Success)
            {
                try
                {
                    var Index = uint.Parse(Match.Groups[1].Value);

                    NodeList.Add(new SequenceElementPathNode(Index));
                }
                catch (Exception e)
                {
                    throw new ParseException(e);
                }

                SequencePart = Match.Groups[2].Value;
            }

            if (SequencePart.Length != 0)
                throw new ParseException("Could not match a sequence element at '" + SequencePart + "'");
        }

        private static void AddNameNode(bool First, ICollection<IPathNode> NodeList, string Part)
        {
            if (First)
            {
                NodeList.Add(new NamePathNode(Part));
            }
            else
            {
                NodeList.Add(new PropertyPathNode(Part));
            }
        }

        #region Nested type: ParseException

        public class ParseException : Exception
        {
            public ParseException(Exception e) : base("Exception during binding path parse", e)
            {
            }

            public ParseException(string Message) : base(Message)
            {
            }
        }

        #endregion
    }
}